package com.yanyeori.framework.core.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.github.junrar.Archive;
import com.github.junrar.exception.RarException;
import com.github.junrar.rarfile.FileHeader;
import lombok.Getter;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.springframework.util.FileCopyUtils;

import java.io.*;
import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * 解压工具类
 * 需要引入依赖：commons-compress、junrar、xz，参考pom.xml
 *
 * @author chenkuan 2024/6/14
 */
public class DecompressUtil {

    @Getter
    public enum CompressType {
        ZIP("zip", DecompressUtil::decompressZip),
        _7Z("7z", DecompressUtil::decompress7z),
        RAR("rar", DecompressUtil::decompressRar),
        TAR("tar", DecompressUtil::decompressTar),
        BZ2("bz2", DecompressUtil::decompressBz2),
        TAR_BZ2("tar.bz2", DecompressUtil::decompressTarBz2),
        TAR_GZ("tar.gz", DecompressUtil::decompressTarGz),
        GZ("gz", DecompressUtil::decompressGz);

        private final String type;
        private final BiFunction<File, String, File> consumer;

        CompressType(String value, BiFunction<File, String, File> consumer) {
            this.type = value;
            this.consumer = consumer;
        }

        public static CompressType getByType(String type) {
            return Arrays.stream(CompressType.values()).filter(x -> StrUtil.equalsIgnoreCase(x.getType(), type)).findFirst().orElse(null);
        }
    }

    /**
     * 是否支持解压缩格式
     *
     * @param file 压缩包文件
     */
    public static boolean support(File file) {
        String suffix = FileUtil.getSuffix(file);
        return Arrays.stream(CompressType.values()).anyMatch(x -> StrUtil.equalsIgnoreCase(x.getType(), suffix));
    }

    /**
     * 解压缩文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompress(File file, String targetPath) {
        CompressType compressType = CompressType.getByType(FileUtil.getSuffix(file));
        Assert.notNull(compressType, "不支持的压缩包格式：" + file.getName());
        return compressType.getConsumer().apply(file, targetPath);
    }

    /**
     * 解压缩文件
     *
     * @param file 压缩包文件
     */
    public static File decompress(File file) {
        return decompress(file, file.getParent() + "/" + FileUtil.mainName(file));
    }

    /**
     * 解压缩文件
     *
     * @param filePath 压缩包文件路径
     */
    public static File decompress(String filePath) {
        return decompress(FileUtil.file(filePath));
    }

    private static void createDirectory(String outputDir, String subDir) {
        FileUtil.mkdir(outputDir + (StrUtil.isEmpty(subDir) ? "" : "/" + subDir));
    }

    /**
     * 解压缩zip文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompressZip(File file, String targetPath) {
        FileInputStream fis = null;
        ZipInputStream zis = null;
        try {
            fis = new FileInputStream(file);
            zis = new ZipInputStream(fis, CharsetUtil.CHARSET_GBK);
            createDirectory(targetPath, null);
            ZipEntry entry;
            while (true) {
                entry = zis.getNextEntry();
                if (entry == null) break;
                if (entry.isDirectory()) {
                    createDirectory(targetPath, entry.getName());
                } else {
                    FileUtil.writeFromStream(zis, FileUtil.file(targetPath + "/" + entry.getName()), false);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("解压缩zip文件失败", e);
        } finally {
            IoUtil.close(zis);
            IoUtil.close(fis);
        }
        return FileUtil.file(targetPath);
    }

    /**
     * 解压缩7z文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompress7z(File file, String targetPath) {
        SevenZFile sevenZFile = null;
        OutputStream outputStream = null;
        try {
            sevenZFile = SevenZFile.builder().setFile(file).get();
            createDirectory(targetPath, null);
            SevenZArchiveEntry entry;
            while ((entry = sevenZFile.getNextEntry()) != null) {
                if (entry.isDirectory()) {
                    createDirectory(targetPath, entry.getName());
                } else {
                    outputStream = new FileOutputStream(targetPath + "/" + entry.getName());
                    int len;
                    byte[] b = new byte[FileCopyUtils.BUFFER_SIZE];
                    while ((len = sevenZFile.read(b)) != -1) {
                        outputStream.write(b, 0, len);
                    }
                    outputStream.flush();
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("解压缩7z文件失败", e);
        } finally {
            IoUtil.close(outputStream);
            IoUtil.close(sevenZFile);
        }
        return FileUtil.file(targetPath);
    }

    /**
     * 解压缩RAR文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompressRar(File file, String targetPath) {
        Archive archive = null;
        OutputStream outputStream = null;
        try {
            archive = new Archive(file);
            FileHeader fileHeader;
            createDirectory(targetPath, null);
            while ((fileHeader = archive.nextFileHeader()) != null) {
                if (fileHeader.isDirectory()) {
                    createDirectory(targetPath, StrUtil.trim(fileHeader.getFileName()));
                } else {
                    outputStream = new FileOutputStream(targetPath + "/" + StrUtil.trim(fileHeader.getFileName()));
                    archive.extractFile(fileHeader, outputStream);
                    outputStream.flush();
                    outputStream.close();
                }
            }
        } catch (RarException | IOException e) {
            throw new RuntimeException("解压缩RAR文件失败", e);
        } finally {
            IoUtil.close(outputStream);
            IoUtil.close(archive);
        }
        return FileUtil.file(targetPath);
    }

    /**
     * 解压缩tar文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompressTar(File file, String targetPath) {
        FileInputStream fis = null;
        TarArchiveInputStream tis = null;
        try {
            fis = new FileInputStream(file);
            tis = new TarArchiveInputStream(fis, FileCopyUtils.BUFFER_SIZE);
            createDirectory(targetPath, null);
            ArchiveEntry entry;
            while (true) {
                entry = tis.getNextEntry();
                if (entry == null) break;
                if (entry.isDirectory()) {
                    createDirectory(targetPath, entry.getName());
                } else {
                    FileUtil.writeFromStream(tis, FileUtil.file(targetPath + "/" + entry.getName()), false);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("解压缩tar文件失败", e);
        } finally {
            IoUtil.close(tis);
            IoUtil.close(fis);
        }
        return FileUtil.file(targetPath);
    }

    /**
     * 解压缩bz2文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompressBz2(File file, String targetPath) {
        FileInputStream fis = null;
        BZip2CompressorInputStream bis = null;
        String suffix = ".bz2";
        try {
            fis = new FileInputStream(file);
            bis = new BZip2CompressorInputStream(fis);
            createDirectory(targetPath, null);
            FileUtil.writeFromStream(bis, FileUtil.file(targetPath + "/" + StrUtil.removeSuffixIgnoreCase(file.getName(), suffix)), false);
        } catch (IOException e) {
            throw new RuntimeException("解压缩bz2文件失败", e);
        } finally {
            IoUtil.close(bis);
            IoUtil.close(fis);
        }
        return FileUtil.file(targetPath);
    }

    /**
     * 解压缩tar.bz2文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompressTarBz2(File file, String targetPath) {
        FileInputStream fis = null;
        BZip2CompressorInputStream bis = null;
        TarArchiveInputStream tis = null;
        try {
            fis = new FileInputStream(file);
            bis = new BZip2CompressorInputStream(fis);
            tis = new TarArchiveInputStream(bis, FileCopyUtils.BUFFER_SIZE);
            createDirectory(targetPath, null);
            ArchiveEntry entry;
            while ((entry = tis.getNextEntry()) != null) {
                if (entry.isDirectory()) {
                    createDirectory(targetPath, entry.getName());
                } else {
                    FileUtil.writeFromStream(tis, FileUtil.file(targetPath + "/" + entry.getName()), false);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("解压缩tar.bz2文件失败", e);
        } finally {
            IoUtil.close(tis);
            IoUtil.close(bis);
            IoUtil.close(fis);
        }
        return FileUtil.file(targetPath);
    }

    /**
     * 解压缩tar.gz文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompressTarGz(File file, String targetPath) {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        GZIPInputStream gzipIn = null;
        TarArchiveInputStream tarIn = null;
        try {
            fis = new FileInputStream(file);
            bis = new BufferedInputStream(fis);
            gzipIn = new GZIPInputStream(bis);
            tarIn = new TarArchiveInputStream(gzipIn, FileCopyUtils.BUFFER_SIZE);
            createDirectory(targetPath, null);
            ArchiveEntry entry;
            while ((entry = tarIn.getNextEntry()) != null) {
                if (entry.isDirectory()) {
                    createDirectory(targetPath, entry.getName());
                } else {
                    FileUtil.writeFromStream(tarIn, FileUtil.file(targetPath + "/" + entry.getName()), false);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("解压缩tar.gz文件失败", e);
        } finally {
            IoUtil.close(tarIn);
            IoUtil.close(gzipIn);
            IoUtil.close(bis);
            IoUtil.close(fis);
        }
        return FileUtil.file(targetPath);
    }

    /**
     * 解压缩gz文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     */
    public static File decompressGz(File file, String targetPath) {
        FileInputStream fis = null;
        GZIPInputStream gzipIn = null;
        String suffix = ".gz";
        try {
            fis = new FileInputStream(file);
            gzipIn = new GZIPInputStream(fis);
            createDirectory(targetPath, null);
            FileUtil.writeFromStream(gzipIn, FileUtil.file(targetPath + "/" + StrUtil.removeSuffixIgnoreCase(file.getName(), suffix)), false);
        } catch (IOException e) {
            throw new RuntimeException("解压缩gz文件失败", e);
        } finally {
            IoUtil.close(gzipIn);
            IoUtil.close(fis);
        }
        return FileUtil.file(targetPath);
    }
}
